﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using System.Reflection;
using zserialize;
using System.Drawing;

namespace zserialize
{
    public static class SerializeScriptTool
    {
        public static string backupDir = "backupDir";


        [MenuItem("zserialize/RestoreAll")]
        static void RestoreAll()
        {
            SerializeScriptTool.Restore(backupDir);

            AssetDatabase.Refresh();
        }

        [MenuItem("zserialize/DeleteBackupDir")]
        static void DeleteBackupDir()
        {
            System.IO.Directory.Delete(backupDir, true);
        }

        [MenuItem("zserialize/SerializeAllScript")]
        static void CustomSerializeScript()
        {
            SerializeScriptTool.SerializeScript(backupDir);
        }

        static bool isScriptSerializable(string codePath)
        {
            MonoScript monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(codePath);
            if(monoScript == null)
            {
                return false;
            }

            System.Type t = monoScript.GetClass();

            if (t != null)
            {
                System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t, true);
                for (int j = 0; j < attrs.Length; j++)
                {
                    if (attrs[j] is zserialize.SerializableAttribute)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        [MenuItem("Assets/zserialize/Restore")]
        static void Restore()
        {
            foreach(var a in Selection.assetGUIDs)
            { 
                string assetPath = AssetDatabase.GUIDToAssetPath(a);
                SerializeScriptTool.Restore(backupDir, assetPath);
                AssetDatabase.Refresh();
            }
        }

        [MenuItem("Assets/zserialize/SerializeAsset")]
        static void SerializeAsset()
        {
            foreach (var a in Selection.assetGUIDs)
            { 
                string assetPath = AssetDatabase.GUIDToAssetPath(a);
                SerializeScriptTool.SerializeAsset(assetPath, backupDir);
            }
        }

        static HashSet<string> serializableScriptPaths;

        static HashSet<string>  GetAllScriptPaths()
        {
            var result = new HashSet<string>();
            string[] codePaths = System.IO.Directory.GetFiles("Assets", "*.cs", System.IO.SearchOption.AllDirectories);
            for (int i = 0; i < codePaths.Length; i++)
            {
                string codePath = codePaths[i];
                if(isScriptSerializable(codePath))
                {
                    result.Add(codePath.Replace('\\', '/'));
                }
            }

            return result;
        }

        public static void SerializeAsset(string assetPath, string backupDir = null, HashSet<string> onlyPathSet = null)
        {
            if(string.IsNullOrEmpty(assetPath))
            {
                return;
            }

            if (!string.IsNullOrEmpty(backupDir))
            {
                if (System.IO.Directory.Exists(backupDir))
                {
                    //throw new System.Exception("backup directory already existed, please manual delete or modity the backup directory path:" + backupDir);
                }
                else
                { 
                    System.IO.Directory.CreateDirectory(backupDir);
                }
            }

            //serializableScriptPaths = GetAllScriptPaths();

            HashSet<string> prefabPathSet = new HashSet<string>();
            prefabPathSet.Add(assetPath);

            HashSet<System.Type> classSet = SerializeScriptForPrefab(backupDir, prefabPathSet, onlyPathSet);


            AssetDatabase.Refresh();
            AssetDatabase.SaveAssets();


        }
        public static void SerializeScript(string backupDir = null, HashSet<string> onlyPathSet = null)
        {
            if(!string.IsNullOrEmpty(backupDir))
            {
                if (System.IO.Directory.Exists(backupDir))
                {
                    //throw new System.Exception("backup directory already existed, please manual delete or modity the backup directory path:" + backupDir);
                }
                else
                {
                    System.IO.Directory.CreateDirectory(backupDir);
                }
            }

            //serializableScriptPaths = GetAllScriptPaths();


            HashSet<string> prefabPathSet = new HashSet<string>();
            string[] prefabPaths = System.IO.Directory.GetFiles("Assets", "*.prefab", System.IO.SearchOption.AllDirectories);
            if (onlyPathSet != null)
            {
                for (int i = 0; i < prefabPaths.Length; i++)
                {
                    string prefabPath = prefabPaths[i];
                    if (onlyPathSet == null || onlyPathSet.Contains(prefabPath))
                    {
                        prefabPathSet.Add(prefabPath);
                    }
                }
            }
            else
            {
        	    for(int i = 0; i < prefabPaths.Length; i++)
        	    {
            	    prefabPathSet.Add(prefabPaths[i]);
                }
            }

            for (int i = 0; i < EditorBuildSettings.scenes.Length; i++)
            {
                EditorBuildSettingsScene scene = EditorBuildSettings.scenes[i];

                if (!string.IsNullOrEmpty(scene.path))
                {
                    if (onlyPathSet == null || onlyPathSet.Contains(scene.path))
                    {
                        prefabPathSet.Add(scene.path);
                    }
                }
                else
                {
                    throw new System.Exception("scene path is null: " + scene.guid + "    " + scene.path);
                }
            }

            string[] guids = AssetDatabase.FindAssets("t:scriptableobject");
            for (int i = 0; i < guids.Length; i++)
            {
                string p = AssetDatabase.GUIDToAssetPath(guids[i]);
                if (onlyPathSet != null)
                {
                    if (onlyPathSet.Contains(p))
                    {
                        prefabPathSet.Add(p);
                    }
                }
                else
                {
                    prefabPathSet.Add(p);
                }
            }

            HashSet<System.Type> classSet = SerializeScriptForPrefab(backupDir, prefabPathSet, onlyPathSet);


            AssetDatabase.Refresh();
            AssetDatabase.SaveAssets();

        }

	    static bool isSerializableType(string scriptPath)
        {
            if (scriptPath == null)
            {
                return false;
            }

            //if (!serializableScriptPaths.Contains(scriptPath))
            //{
            //    return false;
            //}

            if(!isScriptSerializable(scriptPath))
            {
                return false;
            }

            MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath);
            System.Type t = script.GetClass();

            if((t == typeof(ISerializableFieldContainer)) || t.IsAssignableFrom(typeof(ISerializableFieldContainer)))
            {
                return false;
            }

            if (t.IsSubclassOf(typeof(ScriptableObject)))
            {
                return true;
            }

            return true;
        }

        static void DeleteReplacedComponent(object obj)
        {
            if(obj is UnityEngine.SceneManagement.Scene)
            {
                UnityEngine.SceneManagement.Scene scene = (UnityEngine.SceneManagement.Scene)obj;
                GameObject[] rootObjs = scene.GetRootGameObjects();

                for (int i = 0; i < rootObjs.Length; i++)
                {
                    GameObject rootObj = rootObjs[i];
                    DeleteReplacedComponent(rootObj);
                }
            }
            else if (obj is GameObject)
            {
                GameObject prefab = obj as GameObject;
                Dictionary<Object, Object> objMap = new Dictionary<Object, Object>();

                string path = AssetDatabase.GetAssetPath(prefab);

                SerializableFieldContainerBehaviour[] behaviours = prefab.GetComponentsInChildren<SerializableFieldContainerBehaviour>(true);
                for (int i = behaviours.Length - 1; i >= 0; i--)
                {
                    SerializableFieldContainerBehaviour behaviour = behaviours[i];
                    string clsName = behaviour.GetFieldDictionary().className;
                    Object c = behaviour.gameObject.GetComponent(clsName);
                    GameObject.DestroyImmediate(c, true);
                }
            }
        }

        public static long GetLocalFileId(Object target)
        {
            PropertyInfo inspectorModeInfo = typeof(SerializedObject).GetProperty ("inspectorMode", BindingFlags.NonPublic | BindingFlags.Instance);
            SerializedObject srlzedObject = new SerializedObject (target);
            //将srlzedObject的inspectorMode值改成InspectorMode.Debug 为了读取m_LocalIdentfierInFile
            inspectorModeInfo.SetValue (srlzedObject, InspectorMode.Debug, null);
            SerializedProperty localIdProp = srlzedObject.FindProperty ("m_LocalIdentfierInFile") ;
            long localIdentiferID = localIdProp.longValue;
            return localIdentiferID;
        }

        static Dictionary<long, long> GetObjectLocalFieldMap(Object obj)
        {
            Dictionary<long, long> localIdentifierMap = new Dictionary<long, long>();
            if(obj is GameObject)
            {
                GameObject prefabObj = obj as GameObject;
                SerializableFieldContainerBehaviour[] sfcbs = prefabObj.GetComponentsInChildren<SerializableFieldContainerBehaviour>(true);
                for(int i = 0; i < sfcbs.Length; i++)
                {
                    SerializableFieldContainerBehaviour b = sfcbs[i];
                    string clsName = b.GetFieldDictionary().className;
                    Object tc = b.gameObject.GetComponent(clsName);

                    long localFileId0 = GetLocalFileId(tc);
                    long localFileId1 = GetLocalFileId(b);


                    localIdentifierMap.Add(localFileId0, localFileId1);
                }
            }
            return localIdentifierMap;
        }

        static int ReplaceWithDataContainer(string backupDir, string prefabPath, Object obj, HashSet<System.Type> typeSet, bool onlyCheck = false)
        {
            int c = 0;
            if (obj is GameObject)
            {
                GameObject prefab = obj as GameObject;
            
                bool first = true;
                MonoBehaviour[] behaviours = prefab.GetComponentsInChildren<MonoBehaviour>(true);
                for (int i = behaviours.Length - 1; i >= 0; i--)
                {
                    MonoBehaviour behaviour = behaviours[i];
                    if (behaviour == null)
                    {
                        continue;
                    }

                    MonoScript script = MonoScript.FromMonoBehaviour(behaviour);
                    string scripPath = AssetDatabase.GetAssetPath(script);

                    if (!isSerializableType(scripPath))
                    {
                        continue;
                    }

                    c++;

                    if(!onlyCheck)
                    {
                        if(first)
                        {
                            first = false;
                            BackupAsset(backupDir, prefabPath);
                        }

                        SerializableFieldContainerBehaviour container = null;
                        SerializableFieldContainerBehaviour[] containers = behaviour.gameObject.GetComponents<SerializableFieldContainerBehaviour>();
                        for(int j = 0; j < containers.Length; j++)
                        {
                            string className = containers[j].GetFieldDictionary().className;
                            if(className == behaviour.GetType().Name)
                            {
                                container = containers[j];
                                break;
                            }
                        }

                        if(container == null)
                        {
                            container = behaviour.gameObject.AddComponent<SerializableFieldContainerBehaviour>();
                        }


                        Object[] objs = null;
                        byte[] data = SerializableFieldContainerHelper.WriteToBytes(behaviour, out objs, typeSet);
                        container.unityObjs = objs;
                        container.data = data;



                        typeSet.Add(behaviour.GetType());
                    }
                }

            }

            return c;
        }

	    public static HashSet<string> GetDirectDependenicesQuick(string path, Dictionary<string, string> guidToPathMap)
        {
            var result = new HashSet<string>();
            foreach (var a in AssetDatabase.GetDependencies(path, true))
            {
                result.Add(a);
            }
            return result;

            //System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"guid:\s*([^,\}]+)");
            //string str = System.IO.File.ReadAllText(path);

            //HashSet<string> dset = new HashSet<string>();
            //System.Text.RegularExpressions.MatchCollection ms = regex.Matches(str);
            //for (int i = 0; i < ms.Count; i++)
            //{
            //    System.Text.RegularExpressions.Match m = ms[i];
            //    string guid = m.Groups[1].Value;
            //    string assetPath = null;
            //    if (guidToPathMap != null)
            //    {
            //        if (!guidToPathMap.TryGetValue(guid, out assetPath))
            //        {
            //            assetPath = AssetDatabase.GUIDToAssetPath(guid);
            //            guidToPathMap.Add(guid, assetPath);
            //        }
            //    }
            //    else
            //    {
            //        assetPath = AssetDatabase.GUIDToAssetPath(guid);
            //    }

            //    if (!string.IsNullOrEmpty(assetPath))
            //    {
            //        dset.Add(assetPath);
            //    }
            //}

            //return dset;
        }

        static HashSet<System.Type> SerializeScriptForPrefab(string backupDir, HashSet<string> prefabPaths, HashSet<string> onlyPathSet)
        {
            string oldScenePath = EditorSceneManager.GetActiveScene().path;

            Dictionary<string, string> guidToPathMap = new Dictionary<string, string>();
            Dictionary<string, HashSet<string>> prefabMap = new Dictionary<string, HashSet<string>>();
            foreach(var path in prefabPaths)
            {
                HashSet<string> dset = GetDirectDependenicesQuick(path, guidToPathMap);
                prefabMap.Add(path, dset);
            }

            HashSet<System.Type> typeSet = new HashSet<System.Type>();

            Dictionary<string, Dictionary<long, long>> replaceFileIdMap = new Dictionary<string, Dictionary<long, long>>();

            string tempDataAssetPath = "Assets/temp_data.asset";

            HashSet<string> processedPaths = new HashSet<string>();
            foreach (var prefabPath in prefabPaths)
            {
                bool found = false;
                HashSet<string> dset = prefabMap[prefabPath];
                foreach (var p in dset)
                {
                    //if (serializableScriptPaths.Contains(p))
                    if(isScriptSerializable(p))
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    continue;
                }

                processedPaths.Add(prefabPath);

                if (onlyPathSet == null || onlyPathSet.Contains(prefabPath))
                {
				    if (prefabPath.EndsWith(".prefab"))
				    {
					    bool needInstance = false;
					    if(needInstance)
					    {
                            GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
                            if(ReplaceWithDataContainer(backupDir, prefabPath, prefab, typeSet, true) > 0)
                            {
    						    GameObject obj = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
    						    if (obj == null)
    						    {
    							    Debug.LogError("InstantiatePrefab prefab failed: " + prefabPath);
    							    continue;
    						    }

    						    int count = ReplaceWithDataContainer(backupDir, prefabPath, obj, typeSet);
                                if (count > 0)
                                {
                                    PrefabUtility.ApplyPrefabInstance(obj, InteractionMode.AutomatedAction);

                                    Dictionary<long, long> localIdentifierMap = GetObjectLocalFieldMap(prefab);
                                    replaceFileIdMap.Add(prefabPath, localIdentifierMap);
                                }

                                GameObject.DestroyImmediate(obj);
                            }
					    }
					    else
					    {
                            GameObject prefab = PrefabUtility.LoadPrefabContents(prefabPath);

                            int count = ReplaceWithDataContainer(backupDir, prefabPath, prefab, typeSet);
                            if (count > 0)
						    {
                                PrefabUtility.SaveAsPrefabAsset(prefab, prefabPath);

                                Dictionary<long, long> localIdentifierMap = GetObjectLocalFieldMap(prefab);
							    replaceFileIdMap.Add(prefabPath, localIdentifierMap);
						    }
                            PrefabUtility.UnloadPrefabContents(prefab);
					    }
				    }
                    else if (prefabPath.EndsWith(".unity"))
                    {
                        EditorSceneManager.OpenScene(prefabPath);
                        GameObject[] rootObjs = EditorSceneManager.GetActiveScene().GetRootGameObjects();
                        Dictionary<long, long> localIdentifierMap = new Dictionary<long, long>();
                        for (int i = 0; i < rootObjs.Length; i++)
                        {
                            GameObject rootObj = rootObjs[i];
                            Transform[] ts = rootObj.GetComponentsInChildren<Transform>(true);
                            for (int j = 0; j < ts.Length; j++)
                            {
                                // PrefabUtility.DisconnectPrefabInstance(ts[j].gameObject);
                                if (PrefabUtility.GetPrefabInstanceStatus(ts[j].gameObject) != PrefabInstanceStatus.NotAPrefab)
                                    PrefabUtility.UnpackPrefabInstance(ts[j].gameObject, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
                            }


                            int count = ReplaceWithDataContainer(backupDir, prefabPath, rootObj, typeSet);
                            //if (count > 0)
                            //{
                            //    Dictionary<long, long> localIdentifierMap2 = GetObjectLocalFieldMap(rootObj);
                            //    foreach (var entry in localIdentifierMap2)
                            //    {
                            //        localIdentifierMap.Add(entry.Key, entry.Value);
                            //    }
                            //}
                        }



                        EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());

                        for (int i = 0; i < rootObjs.Length; i++)
                        {
                            GameObject rootObj = rootObjs[i];
                           
                           {
                                Dictionary<long, long> localIdentifierMap2 = GetObjectLocalFieldMap(rootObj);
                                foreach (var entry in localIdentifierMap2)
                                {
                                    localIdentifierMap.Add(entry.Key, entry.Value);
                                }
                            }
                        }

                        if (localIdentifierMap.Count > 0)
                        {
                            replaceFileIdMap.Add(prefabPath, localIdentifierMap);
                        }
                    }
                    else if (prefabPath.EndsWith(".asset"))
                    {
                        ScriptableObject scriptableObj = AssetDatabase.LoadAssetAtPath<ScriptableObject>(prefabPath);

                        MonoScript monoScript = MonoScript.FromScriptableObject(scriptableObj);
                        if (monoScript == null)
                        {
                            continue;
                        }

                        string scriptPath = AssetDatabase.GetAssetPath(monoScript);
                        if (!isSerializableType(scriptPath))
                        {
                            continue;
                        }

                        BackupAsset(backupDir, prefabPath);

                        SerializableFieldContainerScriptableObject container = ScriptableObject.CreateInstance<SerializableFieldContainerScriptableObject>();

                        Object[] objs = null;
                        byte[] data = SerializableFieldContainerHelper.WriteToBytes(scriptableObj, out objs, typeSet);
                        container.unityObjs = objs;
                        container.data = data;

                        AssetDatabase.CreateAsset(container, tempDataAssetPath);
                        System.IO.File.Copy(tempDataAssetPath, prefabPath, true);


                        var str = System.IO.File.ReadAllText(prefabPath);
                        str = str.Replace("m_Name: temp_data", $"m_Name: {System.IO.Path.GetFileNameWithoutExtension(prefabPath)}");
                        System.IO.File.WriteAllText(prefabPath, str);

                        //SerializedObject serializedFont = new SerializedObject(AssetDatabase.LoadAssetAtPath<Object>(prefabPath));
                        //SerializedProperty serializedLineSpacing = serializedFont.FindProperty("m_Name");
                        //serializedLineSpacing.stringValue = System.IO.Path.GetFileNameWithoutExtension(prefabPath);
                        //serializedFont.ApplyModifiedProperties();

                        typeSet.Add(scriptableObj.GetType());
                    }
                }
            }
        
            AssetDatabase.DeleteAsset(tempDataAssetPath);

            AssetDatabase.SaveAssets();


            foreach (var entry in replaceFileIdMap)
            {
                string path = entry.Key;
                Dictionary<long, long> localIdentifierMap = entry.Value;

                string str = System.IO.File.ReadAllText(path);

                foreach (var entry2 in localIdentifierMap)
                {
                    string s0 = "&" + entry2.Key + "\n";
                    string s1 = "&" + entry2.Value + "\n";
                    string s2 = "&temp" + entry2.Key + "temp\n";
                    str = str.Replace(s0, s2);
                    str = str.Replace(s1, s0);
                    str = str.Replace(s2, s1);
                }

                System.IO.File.WriteAllText(path, str);
            }


            AssetDatabase.Refresh();


            foreach (var prefabPath in processedPaths)
            {
                if (onlyPathSet == null || onlyPathSet.Contains(prefabPath))
                {
                    if (prefabPath.EndsWith(".prefab"))
                    {
                        GameObject prefab = PrefabUtility.LoadPrefabContents(prefabPath);

                        DeleteReplacedComponent(prefab);

                        PrefabUtility.SaveAsPrefabAsset(prefab, prefabPath);

                        PrefabUtility.UnloadPrefabContents(prefab);


                    }
                    else if (prefabPath.EndsWith(".unity"))
                    {
                        Debug.Log("ready to open scene" + prefabPath);
                        EditorSceneManager.OpenScene(prefabPath);
                        DeleteReplacedComponent(EditorSceneManager.GetActiveScene());
                        EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());
                    }
                }
            }

            if (oldScenePath != EditorSceneManager.GetActiveScene().path)
            {
                if (string.IsNullOrEmpty(oldScenePath))
                {
                    EditorSceneManager.NewScene(NewSceneSetup.EmptyScene);
                }
                else
                {
                    EditorSceneManager.OpenScene(oldScenePath);
                }
            }

            return typeSet;
        }

        static void BackupAsset(string backupDir, string assetPath)
        {
            if(string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(backupDir))
            {
                return;
            }

            string destFilePath = assetPath.Replace("\\", "/");
            destFilePath = destFilePath.Replace("/", "+");
            destFilePath = backupDir + "/" + destFilePath;
            if(System.IO.File.Exists(destFilePath))
            {
                return;
            }

            System.IO.File.Copy(assetPath, destFilePath);
        }

        public static void Restore(string backupDir, string onlyPath = null)
        {
            string[] filePaths = System.IO.Directory.GetFiles(backupDir, "*");
            for(int i = 0; i < filePaths.Length; i++)
            {
                string filePath = filePaths[i];

                string fileName = System.IO.Path.GetFileName(filePath);
                fileName = fileName.Replace("+", "/");
                if(string.IsNullOrEmpty(onlyPath) || onlyPath == fileName)
                { 
                    System.IO.File.Copy(filePath, fileName, true);
                }
            }
        }
    }

}